<?php
defined('DS') or define('DS', DIRECTORY_SEPARATOR);
$ALLGLOBALS = require_once(APP_DIR.DS.'protected'.DS.'config.php');
$http = new Swoole\Http\Server($ALLGLOBALS["host"]["IP"], $ALLGLOBALS["host"]["PORT"]);

if($ALLGLOBALS['debug']){
    error_reporting(-1);
    ini_set("display_errors", "On");
}else{
    error_reporting(E_ALL & ~(E_STRICT|E_NOTICE));
    ini_set("display_errors", "Off");
    ini_set("log_errors", "On");
}

$http->on('start', function ($server) {
    GLOBAL $ALLGLOBALS;
    echo "Swoole http server is started at http://{$ALLGLOBALS["host"]["IP"]}:{$ALLGLOBALS["host"]["PORT"]}\n";
});

$http->on('request', function ($request, $response) {
    $response->header('Access-Control-Allow-Origin', '*'); //解决跨域问题
    if ($request->server['path_info'] == '/favicon.ico' || $request->server['request_uri'] == '/favicon.ico') {
        $response->end();
        return;
    }
    $url = explode('/', trim($request->server['request_uri'], '/'));
    if ($url[0]==null) $array = ["Errors","NotFound"];
    else if (!isset($url[1])) $array = ["Errors","NotFound"]; 
    else $array = $url;
    list($controller, $action) = $array;
    if(!is_available_classname($controller)) $controller = 'Errors';
    if(!is_available_classname($action)) $action = 'NotFound';
    if(!class_exists($controller)) $controller = 'Errors';
    if(!method_exists($controller, $action)) {
        $controller = 'Errors';
        $action = 'NotFound';
    }
    (new $controller)->$action($request, $response);
});

function is_available_classname($name){
    return preg_match('/[a-zA-Z0-9]/', $name);
}

spl_autoload_register('inner_autoload');
function inner_autoload($class){
    $class = str_replace("\\","/",$class);
    foreach(array('model', 'include', 'controller') as $dir){
        $file = APP_DIR.DS.'protected'.DS.$dir.DS.$class.'.php';
        if(file_exists($file)){
            require_once($file);
            return;
        }
        $phpfiles = glob(APP_DIR.DS.'protected'.DS.$dir.DS.'*.php');
        if(is_array($phpfiles)){
            $lowerfile = strtolower($file);
            foreach($phpfiles as $file){
                if(strtolower($file) === $lowerfile){
                    require_once($file);
                    return;
                }
            }
        }
    }
}

class Controller{
    private $_data = array();
    public function init(){}
    public function __construct(){$this->init();}
    public function &__get($name){return $this->_data[$name];}
    public function __set($name, $value){$this->_data[$name] = $value;}
}

class Model{
    public $page;
    public $table_name;
    private $sql = array();
    public function __construct($table_name = null){if($table_name)$this->table_name = $table_name;}
    public function findAll($conditions = array(), $sort = null, $fields = '*', $limit = null){
        $sort = !empty($sort) ? ' ORDER BY '.$sort : '';
        $conditions = $this->_where($conditions);
        $sql = ' FROM '.$this->table_name.$conditions["_where"];
        if(is_array($limit)){
            $total = $this->query('SELECT COUNT(*) as M_COUNTER '.$sql, $conditions["_bindParams"]);
            if(!isset($total[0]['M_COUNTER']) || $total[0]['M_COUNTER'] == 0)return array();
            $limit = $limit + array(1, 10, 10);
            $limit = $this->pager($limit[0], $limit[1], $limit[2], $total[0]['M_COUNTER']);
            $limit = empty($limit) ? '' : ' LIMIT '.$limit['offset'].','.$limit['limit'];
        }else{
            $limit = !empty($limit) ? ' LIMIT '.$limit : '';
        }
        return $this->query('SELECT '. $fields . $sql . $sort . $limit, $conditions["_bindParams"]);
    }
    public function find($conditions = array(), $sort = null, $fields = '*'){
        $res = $this->findAll($conditions, $sort, $fields, 1);
        return !empty($res) ? array_pop($res) : false;
    }
    public function update($conditions, $row){
        $values = array();
        foreach ($row as $k=>$v){
            $values[":M_UPDATE_".$k] = $v;
            $setstr[] = "`{$k}` = ".":M_UPDATE_".$k;
        }
        $conditions = $this->_where( $conditions );
        return $this->execute("UPDATE ".$this->table_name." SET ".implode(', ', $setstr).$conditions["_where"], $conditions["_bindParams"] + $values);
    }
    public function delete($conditions){
        $conditions = $this->_where( $conditions );
        return $this->execute("DELETE FROM ".$this->table_name.$conditions["_where"], $conditions["_bindParams"]);
    }
    public function create($row){
        global $ALLGLOBALS;
        $values = array();
        foreach($row as $k=>$v){
            $keys[] = "`{$k}`"; $values[":".$k] = $v; $marks[] = ":".$k;
        }
        $this->execute("INSERT INTO ".$this->table_name." (".implode(', ', $keys).") VALUES (".implode(', ', $marks).")", $values);
        return $this->dbInstance($ALLGLOBALS['mysql'], 'master')->lastInsertId();
    }
    public function findCount($field,$conditions){
        $conditions = $this->_where( $conditions );
        $count = $this->query("SELECT COUNT(".$field.") AS M_COUNTER FROM ".$this->table_name.$conditions["_where"], $conditions["_bindParams"]);
        return isset($count[0]['M_COUNTER']) && $count[0]['M_COUNTER'] ? $count[0]['M_COUNTER'] : 0;
    }
    public function findSum($field,$conditions){
        $conditions = $this->_where( $conditions );
        $count = $this->query("SELECT sum(".$field.") AS M_SUM FROM ".$this->table_name.$conditions["_where"], $conditions["_bindParams"]);
        return isset($count[0]['M_SUM']) && $count[0]['M_SUM'] ? $count[0]['M_SUM'] : 0;
    }
    public function pager($page, $pageSize, $scope, $total){
        $this->page = null;
        if($total > $pageSize){
            $total_page = ceil($total / $pageSize);
            $page = min(intval(max($page, 1)), $total_page);
            $this->page = array(
                'total_count' => $total,
                'page_size'   => $pageSize,
                'total_page'  => $total_page,
                'first_page'  => 1,
                'prev_page'   => ( ( 1 == $page ) ? 1 : ($page - 1) ),
                'next_page'   => ( ( $page == $total_page ) ? $total_page : ($page + 1)),
                'last_page'   => $total_page,
                'current_page'=> $page,
                'all_pages'   => array(),
                'offset'      => ($page - 1) * $pageSize,
                'limit'       => $pageSize,
            );
            $scope = (int)$scope;
            if($total_page <= $scope ){
                $this->page['all_pages'] = range(1, $total_page);
            }elseif( $page <= $scope/2) {
                $this->page['all_pages'] = range(1, $scope);
            }elseif( $page <= $total_page - $scope/2 ){
                $right = $page + (int)($scope/2);
                $this->page['all_pages'] = range($right-$scope+1, $right);
            }else{
                $this->page['all_pages'] = range($total_page-$scope+1, $total_page);
            }
        }
        return $this->page;
    }
    public function query($sql, $params = array()){return $this->execute($sql, $params, true);}
    public function execute($sql, $params = array(), $readonly = false){
        global $ALLGLOBALS;
        $this->sql[] = $sql;
        $sth = $this->dbInstance($ALLGLOBALS['mysql'], 'master')->prepare($sql);
        if(is_array($params) && !empty($params)){
            foreach($params as $k => &$v){
                if(is_int($v)){
                    $data_type = PDO::PARAM_INT;
                }elseif(is_bool($v)){
                    $data_type = PDO::PARAM_BOOL;
                }elseif(is_null($v)){
                    $data_type = PDO::PARAM_NULL;
                }else{
                    $data_type = PDO::PARAM_STR;
                }
                $sth->bindParam($k, $v, $data_type);
            }
        }
        if($sth->execute())return $readonly ? $sth->fetchAll(PDO::FETCH_ASSOC) : $sth->rowCount();
        $err = $sth->errorInfo();
        err('Database SQL: "' . $sql. '", ErrorInfo: '. $err[2]);
    }
    public function dbInstance($db_config, $db_config_key, $force_replace = false){
        global $ALLGLOBALS;
        if($force_replace || empty($ALLGLOBALS['mysql_instances'][$db_config_key])){
            try {
                if(!class_exists("PDO") || !in_array("mysql",PDO::getAvailableDrivers(), true)){
                    err('Database Err: PDO or PDO_MYSQL doesn\'t exist!');
                }
                $ALLGLOBALS['mysql_instances'][$db_config_key] = new PDO('mysql:dbname='.$db_config['MYSQL_DB'].';host='.$db_config['MYSQL_HOST'].';port='.$db_config['MYSQL_PORT'], $db_config['MYSQL_USER'], $db_config['MYSQL_PASS'], array(PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES \''.$db_config['MYSQL_CHARSET'].'\''));
            }catch(PDOException $e){err('Database Err: '.$e->getMessage());}
        }
        return $ALLGLOBALS['mysql_instances'][$db_config_key];
    }
    private function _where($conditions){
        $result = array( "_where" => " ","_bindParams" => array());
        if(is_array($conditions) && !empty($conditions)){
            $fieldss = array(); $sql = null; $join = array();
            if(isset($conditions[0]) && $sql = $conditions[0]) unset($conditions[0]);
            foreach( $conditions as $key => $condition ){
                if(substr($key, 0, 1) != ":"){
                    unset($conditions[$key]);
                    $conditions[":".$key] = $condition;
                }
                $join[] = "`{$key}` = :{$key}";
            }
            if(!$sql) $sql = join(" AND ",$join);

            $result["_where"] = " WHERE ". $sql;
            $result["_bindParams"] = $conditions;
        }
        return $result;
    }
}
function err($msg){
    echo $msg;
}
class Errors {
    public function NotFound($request, $response) {
        $response->header('Content-Type', 'application/json');
        $response->end('{"code":500,"msg":"error，controller of action is not exists！"}');
    }
}
$http->start();